 1.Kudu概述
   
   背景
   Apache Kudu是由Cloudera开源的存储引擎，可以同时提供低延迟的随机读写和高效的数据分析能力。Kudu支持水
平扩展，使用Raft协议进行一致性保证，并且与Cloudera Impala和Apache Spark等当前流行的大数据查询和分析工
具结合紧密。
   近两年，KUDU 在大数据平台的应用越来越广泛。在阿里、小米、网易等公司的大数据架构中，KUDU 都有着不可替
代的地位。
   现在提起大数据存储，我们能想到的技术有很多，比如HDFS，以及在HDFS上的列式存储技术Apache Parquet，
Apache ORC，还有以KV形式存储半结构化数据的Apache HBase和Apache Cassandra等等。既然有了如此多的存
储技术，Cloudera公司为什么要开发出一款全新的存储引擎Kudu呢？
   基于HDFS的存储技术：
   数据分析：Parquet，具有高吞吐量连续读取数据的能力
   实时读写：HBase和Cassandra等技术适用于低延迟的随机读写场景
   
   在 KUDU 之前，大数据主要以两种方式存储：
       静态数据：以 HDFS 引擎作为存储引擎，适用于高吞吐量的离线大数据分析场景。这类存储的局限性是数据无
法进行随机的读写。
       动态数据：以 HBase、Cassandra 作为存储引擎，适用于大数据随机读写场景。这类存储的局限性是批量读取
吞吐量远不如 HDFS，不适用于批量数据分析的场景。
   所以现在的企业中，经常会存储两套数据分别用于实时读写与数据分析，
   先将数据写入HBase中，再定期通过ETL到Parquet进行数据同步。
   但是这样做有很多缺点：
       用户需要在两套系统间编写和维护复杂的ETL逻辑。结构复杂，维护成本高。
	   时效性较差。因为ETL通常是一个小时、几个小时甚至是一天一次，那么可供分析的数据就需要一个小时至一天
的时间后才进入到可用状态，也就是说从数据到达到可被分析之间是会存在一个较为明显的“空档期”的。
       更新需求难以满足。在实际情况中可能会有一些对已经写入的数据的更新需求，这种情况往往需要对历史数据
进行更新，而对Parquet这种静态数据集的更新操作，代价是非常昂贵的。
       存储资源浪费。两套存储系统意味着占用的磁盘资源翻倍了，造成了成本的提升。
   
   我们知道，基于HDFS的存储技术，比如Parquet，具有高吞吐量连续读取数据的能力；而HBase和Cassandra等技术
适用于低延迟的随机读写场景，那么有没有一种技术可以同时具备这两种优点呢？Kudu提供了一种“happy
medium”的选择：
   Kudu不但提供了行级的插入、更新、删除API，同时也提供了接近Parquet性能的批量扫描操作。使用同一份存储，
既可以进行随机读写，也可以满足数据分析的要求。
   数据模型
   
   KUDU 的数据模型与传统的关系型数据库类似，一个 KUDU 集群由多个表组成，每个表由多个字段组成，一个表必
须指定一个由若干个(>=1)字段组成的主键，如下图：
   从用户角度来看，
   Kudu是一种存储结构化数据表的存储系统。
   在一个Kudu集群中可以定义任意数量的table，每个table都需要预先定义好schema。
   每个table的列数是确定的，每一列都需要有名字和类型，每个表中可以把其中一列或多列定义为主键。
   这么看来，Kudu更像关系型数据库，而不是像HBase、Cassandra和MongoDB这些NoSQL数据库。不过Kudu目前
还不能像关系型数据一样支持二级索引。 Kudu使用确定的列类型，字段是强类型的，而不是类似于NoSQL的
“everything is byte”。这可以带来两点好处：
       确定的列类型使Kudu可以进行类型特有的编码，节省空间。
       可以提供 SQL-like 元数据给其他上层查询工具，比如BI工具。
	   KUDU 的使用场景是 OLAP 分析，有一个数据类型对下游的分析工具也更加友好。
   用户可以使用 Insert，Update和Delete API对表进行写操作.不论使用哪种API,都必须指定主键。但批量的删除和
更新操作需要依赖更高层次的组件（比如Impala、Spark）。Kudu目前还不支持多行事务。 而在读操作方面，Kudu
只提供了Scan操作来获取数据。用户可以通过指定过滤条件来获取自己想要读取的数据，但目前只提供了两种类型
的过滤条件：主键范围和列值与常数的比较。由于Kudu在硬盘中的数据采用列式存储，所以只扫描需要的列将极大
地提高读取性能。
   
   一致性模型
   内部事务隔离
   Kudu为用户提供了两种一致性模型。默认的一致性模型是snapshot consistency。这种一致性模型保证用户每次读
取出来的都是一个可用的快照，但这种一致性模型只能保证单个client可以看到最新的数据，但不能保证多个client每
次取出的都是最新的数据。
   另一种一致性模型external consistency可以在多个client之间保证每次取到的都是最新数据，但是Kudu没有提供默
认的实现，需要用户做一些额外工作。
   为了实现external consistency，Kudu提供了两种方式：
       在client之间传播timestamp token。在一个client完成一次写入后，会得到一个timestamp token，然后这个
client把这个token传播到其他client,这样其他client就可以通过token取到最新数据了.不过这个方式的复杂度很高。
       通过commit-wait方式，这有些类似于Google的Spanner。但是目前基于NTP的commit-wait方式延迟实在有点
高。不过Kudu相信，随着Spanner的出现，未来几年内基于real-time clock的技术将会逐渐成熟。

 2.Kudu的架构
   
   与HDFS和HBase相似，Kudu使用单个的Master节点，用来管理集群的元数据，并且使用任意数量的Tablet Server
节点用来存储实际数据。可以部署多个Master节点来提高容错性。
   Master
   Kudu的master节点负责整个集群的元数据管理和服务协调。它承担着以下功能：
       作为catalog manager，master节点管理着集群中所有table和tablet的schema及一些其他的元数据。
	   作为cluster coordinator，master节点追踪着所有server节点是否存活，并且当server节点挂掉后协调数据的
重新分布。
       作为tablet directory，master跟踪每个tablet的位置。
   
   Catalog Manager
   Kudu的master节点会持有一个单tablet的table——catalog table，但是用户是不能直接访问的。master将内部的
catalog信息写入该tablet，并且将整个catalog的信息缓存到内存中。随着现在商用服务器上的内存越来越大，并且
元数据信息占用的空间其实并不大，所以master不容易存在性能瓶颈。catalog table保存了所有table的schema的
版本以及table的状态（创建、运行、删除等）。
   
   Cluster Coordination
   Kudu集群中的每个tablet server都需要配置master的主机名列表。当集群启动时，tablet server会向master注册，
并发送所有tablet的信息。tablet server第一次向master发送信息时会发送所有tablet的全量信息，后续每次发送则
只会发送增量信息，仅包含新创建、删除或修改的tablet的信息。 作为cluster coordination，master只是集群状态
的观察者。对于tablet server中tablet的副本位置、Raft配置和schema版本等信息的控制和修改由tablet server自身
完成。master只需要下发命令，tablet server执行成功后会自动上报处理的结果。
   
   Tablet Directory
   因为master上缓存了集群的元数据，所以client读写数据的时候，肯定是要通过master才能获取到tablet的位置等信
息。但是如果每次读写都要通过master节点的话，那master就会变成这个集群的性能瓶颈，所以client会在本地缓存
一份它需要访问的tablet的位置信息，这样就不用每次读写都从master中获取。 因为tablet的位置可能也会发生变化
(比如某个tablet server节点crash掉了)，所以当tablet的位置发生变化的时候，client会收到相应的通知，然后再
去master上获取一份新的元数据信息。
   
   Table
   在数据存储方面，Kudu选择完全由自己实现，而没有借助于已有的开源方案。tablet存储主要想要实现的目标为：
       快速的列扫描
       低延迟的随机读写
       一致性的性能
   
   RowSets
   在Kudu中，tablet被细分为更小的单元，叫做RowSets。一些RowSet仅存在于内存中，被称为MemRowSets，而另
一些则同时使用内存和硬盘，被称为DiskRowSets。任何一行未被删除的数据都只能存在于一个RowSet中。 无论任
何时候，一个tablet仅有一个MemRowSet用来保存最新插入的数据，并且有一个后台线程会定期把内存中的数据
flush到硬盘上。 当一个MemRowSet被flush到硬盘上以后，一个新的MemRowSet会替代它。而原有的MemRowSet会
变成一到多个DiskRowSet。flush操作是完全同步进行的，在进行flush时，client同样可以进行读写操作。
   
   MemRowSet
   MemRowSets是一个可以被并发访问并进行过锁优化的B-tree，主要是基于MassTree来设计的，但存在几点不同：
      Kudu并不支持直接删除操作，由于使用了MVCC，所以在Kudu中删除操作其实是插入一条标志着删除的数据，
这样就可以推迟删除操作。
      类似删除操作，Kudu也不支持原地更新操作。
      将tree的leaf链接起来，就像B+-tree。这一步关键的操作可以明显地提升scan操作的性能。
      没有实现字典树（trie树），而是只用了单个tree，因为Kudu并不适用于极高的随机读写的场景
   
   与Kudu中其他模块中的数据结构不同，MemRowSet中的数据使用行式存储。因为数据都在内存中，所以性能也是
可以接受的，而且Kudu对在MemRowSet中的数据结构进行了一定的优化。   
   DiskRowSet
   当MemRowSet被flush到硬盘上，就变成了DiskRowSet。当MemRowSet被flush到硬盘的时候，每32M就会形成一
个新的DiskRowSet，这主要是为了保证每个DiskRowSet不会太大，便于后续的增量compaction操作。Kudu通过将
数据分为base data和delta data，来实现数据的更新操作。Kudu会将数据按列存储，数据被切分成多个page，并
使用B-tree进行索引。除了用户写入的数据，Kudu还会将主键索引存入一个列中，并且提供布隆过滤器来进行高效
查找。
   
   Compaction
   为了提高查询性能，Kudu会定期进行compaction操作，合并delta data与base data，对标记了删除的数据进行删
除，并且会合并一些DiskRowSet。
   
   分区
   选择分区策略需要理解数据模型和表的预期工作负载:
       对于写量大的工作负载，重要的是要设计分区，使写分散在各个tablet上，以避免单个tablet超载。
	   对于涉及许多短扫描的工作负载(其中联系远程服务器的开销占主导地位)，如果扫描的所有数据都位于同一块
tablet上，则可以提高性能。
   理解这些基本的权衡是设计有效分区模式的核心。
   没有默认分区 在创建表时，Kudu不提供默认的分区策略。建议预期具有繁重读写工作负载的新表至少拥有与tablet
服务器相同的tablet。
  
   和许多分布式存储系统一样，Kudu的table是水平分区的。BigTable只提供了range分区，Cassandra只提供hash分
区，而Kudu同时提供了这两种分区方式，使分区较为灵活。当用户创建一个table时，可以同时指定table的的
partition schema，partition schema会将primary key映射为partition key。一个partition schema包括0到多个
hash-partitioning规则和一个range-partitioning规则。通过灵活地组合各种partition规则，用户可以创造适用于自
己业务场景的分区方式。
   